﻿using System;
using System.Collections.Generic;
using System.Text;
using Expect.Core.BaseClasses;
using Expect.Core.Errors;
using Expect.Core.Interfaces;

namespace Expect.Core
{
    using Expect.Core.Helpers;

    /// <summary>
    /// Definition of a specification
    /// </summary>
    public class SpecificationDefinition : RunnableActiveList<FixtureDefinition, ISpecificationImplementation>
    {
        private string _title;

        /// <summary>
        /// Execution context for specification
        /// </summary>
        public string Title
        {
            get
            {
                if (!string.IsNullOrEmpty(_title))
                    return this._title;

                return Name;
            }
            set
            {
                this._title = value;
            }
        }

        /// <summary>
        /// Execution context for specification
        /// </summary>
        public IExecutionContext Context { get; set; }

        /// <summary>
        /// Fixtures to run for spec setup
        /// </summary>
        public SpecificationDefinition Setup { get; set; }

        /// <summary>
        /// Fixtures to run for spec teardown
        /// </summary>
        public SpecificationDefinition Teardown { get; set; }

        /// <summary>
        /// The fixtures of the specification
        /// </summary>
        public IList<FixtureDefinition> Fixtures
        {
            get { return ChildElements; }
        }

        /// <summary>
        /// The parent specification
        /// </summary>
        public SpecificationDefinition ParentSpecification { get; set; }

        private IList<SpecificationDefinition> _childSpecs = new List<SpecificationDefinition>();
        /// <summary>
        /// Childs specifications
        /// </summary>
        public IList<SpecificationDefinition> ChildSpecs
        {
            get { return _childSpecs; }
            set { _childSpecs = value; }
        }

        private Dictionary<string, object> _ht;
        /// <summary>
        /// The execution params
        /// </summary>
        public Dictionary<string, object> ExecutionParams
        {
            get
            {
                if (_ht == null)
                    _ht = new Dictionary<string, object>();

                return _ht;
            }
        }    
    
        /// <summary>
        /// Add a spec to the childs list
        /// </summary>
        /// <param name="spec"></param>
        public void AddChildSpec(SpecificationDefinition spec)
        {
            spec.ParentSpecification = this;
            ChildSpecs.Add(spec);
        }

        private void AddFixture(FixtureDefinition fixture)
        {
            if (Context != null)
            {
                this.EndLastFixtureRun();
            }
            Fixtures.Add(fixture);
        }

        /// <summary>
        /// Add a fixture
        /// </summary>
        /// <param name="act"></param>
        /// <returns></returns>
        public SpecificationDefinition AddFixture(Action<FixtureDefinition> act)
        {
            FixtureDefinition fixture = new FixtureDefinition(this);
            act(fixture);
            this.AddFixture(fixture);
            return this;
        }

        /// <summary>
        /// Add a fixture
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="act"></param>
        /// <returns></returns>
        public SpecificationDefinition AddFixture<T>(Action<FixtureDefinition> act) where T:IFixtureImplementation
        {
            FixtureDefinition fixture = new FixtureDefinition<T>(this);
            act(fixture);
            this.AddFixture(fixture);
            return this;
        }

        /// <summary>
        /// Add a fixture
        /// </summary>
        /// <returns></returns>
        public FixtureDefinition AddFixture()
        {
            FixtureDefinition def = new FixtureDefinition(this);
            this.AddFixture(def);
            return def;
        }

        /*
        /// <summary>
        /// Add a fixture
        /// </summary>
        /// <param name="ctorArgs"></param>
        /// <returns></returns>
        public FixtureDefinition AddFixture(params string[] ctorArgs)
        {
            FixtureDefinition def = new FixtureDefinition(this);
            def.Arguments = new FixtureRow();
            foreach (string s in ctorArgs)
                def.Arguments.Add(new FixtureCell{Text = s});
            this.AddFixture(def);
            return def;
        }*/

        /// <summary>
        /// Add a fixture
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public FixtureDefinition AddFixture<T>() where T:IFixtureImplementation
        {
            FixtureDefinition<T> def = new FixtureDefinition<T>(this);
            this.AddFixture(def);
            return def;
        }

        /*
        /// <summary>
        /// Add a fixture
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="ctorArgs"></param>
        /// <returns></returns>
        public FixtureDefinition AddFixture<T>(params string[] ctorArgs) where T : IFixtureImplementation
        {
            FixtureDefinition<T> def = new FixtureDefinition<T>(this);
            def.Arguments = new FixtureRow();
            foreach (string s in ctorArgs)
                def.Arguments.Add(new FixtureCell { Text = s });
            this.AddFixture(def);
            return def;
        }*/

        /// <summary>
        /// Initialize the specification
        /// </summary>
        public virtual void Initialize()
        {
            
        }

        /// <summary>
        /// Run the specification
        /// </summary>
        public void Run()
        {
            if (Context == null)
            {
                throw new InvalidOperationException("You must specify execution context");
            }

            Context.Resolve(this);
            SpecificationDefinition setup = GetSetup();

            if (setup != null)
            {
                setup.Context = Context;
                setup.Run();
                if (!setup.IsSuccessful)
                {
                    this.Status.Success = setup.Status.Success;
                    this.Status.AddError(new SetupError());                    
                }
            }
            
            foreach (var fixture in Fixtures)
            {
                fixture.Run();
                fixture.EndRun();
            }

            foreach (var spec in ChildSpecs)
            {
                spec.Context = Context;
                spec.Run();
            }

            this.CalculateResult();            
        }

        /// <summary>
        /// Calculate fixture result
        /// </summary>
        public override bool? CalculateResult()
        {
            if (!_ended)
                this.EndRun();

            var res = base.CalculateResult();

            if (!res.HasValue && Fixtures.Count == 0)
            {
                res = true;
            }

            foreach (var spec in ChildSpecs)
            {
                if (!spec.IsSuccessful)
                {
                    res = spec.Status.Success;
                }
            }
            Status.Success = res;

            return res;
        }

        private bool _ended;

        /// <summary>
        /// End specification run
        /// </summary>
        public void EndRun()
        {
            _ended = true; 
            EndLastFixtureRun();            
        }

        private void EndLastFixtureRun()
        {
            if (Fixtures.Count == 0) return;

            var fixture = this.Fixtures[this.Fixtures.Count - 1];
            if (fixture.Status.Success == null)
            {
                fixture.EndRun();
            }
        }

        private SpecificationDefinition GetSetup()
        {
            var setup = this.Setup;
            var curspec = this.ParentSpecification;
            while(setup == null && curspec != null)
            {
                setup = curspec.Setup;
                curspec = curspec.ParentSpecification;
            }

            return setup;
        }

        /// <summary>
        /// Get a string representation of all errors
        /// </summary>
        /// <returns></returns>
        public string GetMessageForInconclusiveResult()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Expect could not resolve this specification. Please check the following possible causes");
            this.GetErrorsStack(string.Empty, sb);
            var errors = sb.ToString();
            return errors;
        }

        /// <summary>
        /// Build a string representation of errors
        /// </summary>
        /// <param name="prefix">errors prefix</param>
        /// <param name="sb">errors buffer</param>
        public override void GetErrorsStack(string prefix, StringBuilder sb)
        {
            SpecificationDefinition setup = GetSetup();
            if (setup != null && !setup.IsSuccessful && this.Setup == setup)
            {
                sb.AppendLine(prefix + "Setup errors:");
                setup.GetErrorsStack(prefix + "\t", sb);                
            }

            if (sb.Length > 0 && Fixtures.Count > 0)
            {
                sb.AppendLine(prefix + "Specification errors:");
            }
            base.GetErrorsStack(prefix, sb);

            foreach (var spec in ChildSpecs)
            {
                sb.AppendLine(prefix + "Child specification errors:");
                spec.GetErrorsStack(prefix + "\t", sb);
            }
        }

        /// <summary>
        /// Get a string representation of all errors
        /// </summary>
        /// <returns></returns>
        public string GetMessageForFailedResult()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Expect specification failed. Please check the following possible causes");
            this.GetErrorsStack(string.Empty,sb);
            var errors = sb.ToString();
            return errors;
        }
    }

    /// <summary>
    /// Definition of a specification
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class SpecificationDefinition<T> : SpecificationDefinition where T : AbstractSpecImplementation
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public SpecificationDefinition():base()
        {
            ClassType = typeof(T);
        }
    }
}
